#version 330
#extension GL_EXT_geometry_shader4 : enable
layout(points) in;
layout(line_strip) out;

// ilosc wierzcholkow z ktorych sklada sie dana krzywa (cala)
uniform int inVertices;
uniform  mat4 inProjectionMatrix;

in vec3 geomColor[1];
in mat4 bezierPoints[1];

out vec3 exColor;

mat3 segmentWeights = mat3(0.75f, 0.25, 0.0f,
					0.25f, 0.5f, 0.1666666666f,
					0.0f, 0.25f, 0.75f);

int toUseVerticesForSegment(int id, int inVertices)
{
	int toUse = ((id+1)*3)+1;

	if(toUse > inVertices)
		toUse = 4 - toUse + inVertices;
	else
		toUse = 4;
	return toUse;
}

mat4 affineMatrix(mat4 m)
{
	mat4 res = mat4(m[0] / abs(m[0].w),
				m[1] / abs(m[1].w),
				m[2] / abs(m[2].w),
				m[3] / abs(m[3].w));
	res[0].z = m[0].z;
	res[1].z = m[1].z;
	res[2].z = m[2].z;
	res[3].z = m[3].z;
	return res;
}

// bezierPointsT - transposed matri
bool isSegmentVisible(mat4 affine)
{
	affine = transpose(affine);
	vec4 one = vec4(1.0f), mOne = vec4(-1.0f);

	// all x components are <= -1 - not visible
	if(all(lessThanEqual(affine[0], mOne))) 
		return false;
	// all x components are >= 1 - not visible
	if(all(greaterThanEqual(affine[0], one))) 
		return false;
	// all y components are <= -1 - not visible
	if(all(lessThanEqual(affine[1], mOne))) 
		return false;
	// all y components are >= 1 - not visible
	if(all(greaterThanEqual(affine[1], one))) 
		return false;
	// all z components are <= -1 - not visible
	if(all(lessThanEqual(affine[2], mOne))) 
		return false;
	//// all z components are <= -1 - not visible
	//if(all(greaterThanEqual(affine[2], vec4(2)))) 
	//	return false;
	return true;
}

// t = [0,1]
float bezierCoef(float t, int toUse, vec4 b0)
{
	mat4 b = mat4(b0, vec4(0), vec4(0), vec4(0));
	float u = 1-t;
	for(int j=1; j<toUse; ++j)
	{
		int n = toUse - j;
		for(int i=0; i<n; ++i)
		{
			b[j][i] = b[j-1][i] * u + b[j-1][i+1] * t;
		}
	}
	return b[toUse-1][0];
}

vec3 bezierPos(float t, int toUse, mat4 b)
{
	vec3 pos;
	pos.x = bezierCoef(t, toUse, b[0]); // first column
	pos.y = bezierCoef(t, toUse, b[1]);
	pos.z = bezierCoef(t, toUse, b[2]);
	return pos;
}

void emmitPoints(int toUse, mat4 bezierPoints, float start, float end, float step)
{
	float t = start;
	bool work = true;
	while(work)
	{
		if(t > end)
		{
			t = end;
			work = false;
		}
		gl_Position = inProjectionMatrix * vec4(bezierPos(t, toUse, bezierPoints), 1);
		gl_Position.z = 0.0f;
		EmitVertex();
		t = t + step;
	}
	EndPrimitive();
}

float getWeightedSegmentStep(mat4 affineM, int segm)
{
	vec3 x = abs(vec3(affineM[1].x - affineM[0].x,
						affineM[2].x - affineM[1].x,
						affineM[3].x - affineM[2].x));
	vec3 y = abs(vec3(affineM[1].y - affineM[0].y,
						affineM[2].y - affineM[1].y,
						affineM[3].y - affineM[2].y));
	x = segmentWeights * x;
	y = segmentWeights * y;
	x[segm] = max(x[segm] , y[segm]);

	x[segm] = clamp(x[segm], 0.02, 1.2f);

	if(isnan(x[segm]))
		x[segm] = 0.05f;
	return  0.02 / x[segm];
}

void emmitSegment(int toUse, mat4 bezierPoints, int segm, mat4 affineM)
{
	float step = getWeightedSegmentStep(affineM, segm);
	//float step = 0.01;
	if(segm == 0)
	{
		emmitPoints(toUse, bezierPoints, 0.0f, 0.3333333f, step);
	}
	else if(segm == 1)
	{
		emmitPoints(toUse, bezierPoints, 0.3333333f, 0.6666666f, step);
	}
	else if(segm == 2)
	{
		emmitPoints(toUse, bezierPoints, 0.6666666f, 1.0, step);
	}
}

void drawBezierPolygon(int toUse, mat4 points)
{
	for(int i = 0; i< toUse; ++i)
	{
		gl_Position = points[i];
		gl_Position.z = 0.0;
		EmitVertex();		
	}
	EndPrimitive();
}

void main(void)
{
	exColor = geomColor[0];
	gl_Position = vec4(1.0);
	int id = int(gl_PrimitiveIDIn * 0.336f);

	int toUse = toUseVerticesForSegment(id, inVertices);
	mat4 affine = affineMatrix(bezierPoints[0]);
	mat4 bezierPointsT = transpose(bezierPoints[0]);
	
	if ( isSegmentVisible(affine) == false) // wszystkie punkty sa za plaszczyzna obcinania, w ogole nie rysujemy!
		return;

	int segm = gl_PrimitiveIDIn % 3;
	emmitSegment(toUse, bezierPointsT, segm, affine);

	exColor = vec3(0,1,1);	
	//drawBezierPolygon(toUse, affine);
	return;	
}